#pragma once

#include <GL/glew.h>
#include <iostream>
#include <vector>
#include <string>

namespace cagd
{
    class ShaderProgram
    {
    protected:
        // handles of objects
        std::vector<GLuint>         _vertex_shaders;
        std::vector<GLuint>         _fragment_shaders;
        GLuint                      _program;

        // file names of sources
        std::vector<std::string>    _vertex_shader_file_names;
        std::vector<std::string>    _fragment_shader_file_names;

        // sources
        std::vector<std::string>    _vertex_shader_sources;
        std::vector<std::string>    _fragment_shader_sources;

        // status values
        std::vector<GLint>          _vertex_shader_compiled;
        std::vector<GLint>          _fragment_shader_compiled;
        GLint                       _linked;

        // log
        GLboolean   _ListOpenGLErrors(const char *file_name, GLint line, std::ostream& output = std::cout) const;  // returns true if an OpenGL error occurred, false otherwise
        GLvoid      _ListVertexShaderInfoLog(GLuint index, std::ostream& output = std::cout) const;
        GLvoid      _ListFragmentShaderInfoLog(GLuint index, std::ostream& output = std::cout) const;
        GLvoid      _ListProgramInfoLog(std::ostream& output = std::cout) const;
        GLvoid      _ListValidateInfoLog(std::ostream& output = std::cout) const;

    public:
        // default constructor
        ShaderProgram();

        // links and installs the shader program
        GLvoid CreateProgram(GLboolean logging_is_enabled = false, std::ostream &output = std::cout);

        // relinks the shader program
        GLboolean LinkProgram(GLboolean logging_is_enabled = false, std::ostream &output = std::cout);

        // adds - removes shaders
        GLboolean   AddVertexShader(GLuint index, const std::string &file_name, GLboolean logging_is_enabled = false, std::ostream &output = std::cout);
        GLvoid      RemoveVertexShader(GLuint index);
        GLboolean   AddFragmentShader(GLuint index, const std::string &file_name, GLboolean logging_is_enabled = false, std::ostream &output = std::cout);
        GLvoid      RemoveFragmentShader(GLuint index);

        // attaches - detaches shaders
        GLboolean AttachVertexShader(GLuint index, GLboolean logging_is_enabled = false, std::ostream &output = std::cout);
        GLvoid DettachVertexShader(GLuint index);
        GLboolean AttachFragmentShader(GLuint index, GLboolean logging_is_enabled = false, std::ostream &output = std::cout);
        GLvoid DettachFragmentShader(GLuint index);

        GLboolean SetUniformVariable1i(const GLchar *name, GLint parameter) const;
        GLboolean SetUniformVariable1f(const GLchar *name, GLfloat parameter) const;
        GLboolean SetUniformVariable2f(const GLchar *name, GLfloat parameter_1, GLfloat parameter_2) const;
        GLboolean SetUniformVariable3f(const GLchar *name, GLfloat parameter_1, GLfloat parameter_2, GLfloat parameter_3) const;
        // ...

        GLint GetUniformVariableLocation(const GLchar *name, GLboolean logging_is_enabled = false, std::ostream& output = std::cout) const;

        GLvoid Disable() const;
        GLvoid Enable(GLboolean logging_is_enabled = false, std::ostream& output = std::cout) const;

        virtual ~ShaderProgram();
    };
}
